#include <stdio.h>
#include "segment.h"
#include "Box.h"

const char *strSegmentIntersectionType( _t_segmentIntersectionType it) {
  const char *ret = "N/A";
  switch( it) {
  case SEG_NO_INTERSECTION:             ret = "No intersection";                   break;
  case SEG_INTERIOR_POINT_INTERSECTION: ret = "Intersection: point";               break;
  case SEG_ENDPOINT_INTERSECTION:       ret = "Intersection: end point";           break;
  case SEG_ENDPOINT_SHARED:             ret = "End point shared";                  break;
  case SEG_INTERIOR_SEGMENT:            ret = "Interior segment";                  break;
  case SEG_SEGMENT_INTERSECTION:        ret = "Intersection: segment piece";       break;
  case SEG_COINCIDENT_SEGMENTS:         ret = "Intersection: coincident segments"; break;
  default: break;
  }
  return ret;
}


Punto calculateLineLineIntersection2D( const Segment &s1, const Segment &s2, float eps) {
  // Punto u = s1.end() - s1.begin();
  float ux = s1.end().x() - s1.begin().x();
  float uy = s1.end().y() - s1.begin().y();
  float uz = s1.end().z() - s1.begin().z();
  // Punto v = s2.end() - s2.begin();
  float vx = s2.end().x() - s2.begin().x();
  float vy = s2.end().y() - s2.begin().y();
  // Punto d = s1.begin() - s2.begin();
  float dx = s1.begin().x() - s2.begin().x();
  float dy = s1.begin().y() - s2.begin().y();
  // float dem = u.y * v.x - u.x * v.y;
  float dem = uy * vx - ux * vy;
  if ( fabsf( dem) < eps) {
    printf( "ERROR: Undetermined denominator!!!!\n");
    return Punto( 0.0f, 0.0f, 0.0f);
  }
  // float a = ( d.x * v.y - d.y * v.x) / dem;
  // Punto ret;
  // ret = a * u + s1.begin;
  // return ret;
  float a = ( dx * vy - dy * vx) / dem;
  return Punto( a * ux + s1.begin().x(),
		a * uy + s1.begin().y(),
		a * uz + s1.begin().z());
}

static bool dentroSegmento( const Segment &s1, const Punto &p, float eps) {
  Box bb;
  bb.init( s1.begin());
  bb.update( s1.end());
  return bb.dentro( p, eps);
}

_t_segmentIntersectionType checkIntersection( const Segment &s1, const Segment &s2,
					      Punto *dstPoint, Segment *dstSegment, float eps) {
  float a1 = areaTriangle( s1.begin(), s1.end(), s2.begin());
  float a2 = areaTriangle( s1.begin(), s1.end(), s2.end());
  float b1 = areaTriangle( s2.begin(), s2.end(), s1.begin());
  float b2 = areaTriangle( s2.begin(), s2.end(), s1.end());

  // if ( ( fabsf( a1) > eps) && ( fabsf( a2) > eps) && 
  //      ( fabsf( b1) > eps) && ( fabsf( b2) > eps)) {
  // esto de abajo es mas rapido que lo de arriba!
  if ( fabsf( a1) < eps) a1 = 0.0;
  if ( fabsf( a2) < eps) a2 = 0.0;
  if ( fabsf( b1) < eps) b1 = 0.0;
  if ( fabsf( b2) < eps) b2 = 0.0;
  float prod = a1 * a2 * b1 * b2;
  if ( prod != 0.0) {
    if ( ( ( a1 * a2) < eps) && ( ( b1 * b2 ) < eps)) {
      if ( dstPoint) {
	Punto tmp = calculateLineLineIntersection2D( s1, s2, eps);
	dstPoint->set( tmp);
      }
      return SEG_INTERIOR_POINT_INTERSECTION;
    } else {
      return SEG_NO_INTERSECTION;
    }
  }
       
  // vemos si comparten algun nodo:
  int sharedNodes = 0;
  Punto tmpP;
  if ( coincidentPoints( s1.begin(), s2.begin(), eps)) { tmpP = s1.begin(); sharedNodes++; }
  if ( coincidentPoints( s1.begin(), s2.end(), eps)) { tmpP = s1.begin(); sharedNodes++; }
  if ( coincidentPoints( s1.end(), s2.begin(), eps)) { tmpP = s1.end(); sharedNodes++; }
  if ( coincidentPoints( s1.end(), s2.end(), eps)) { tmpP = s1.end(); sharedNodes++; }
  // puede que alguno de ellos sea degenerado:
  if ( sharedNodes > 0) {
    _t_segmentIntersectionType ret = SEG_ENDPOINT_SHARED; // caso 1
    if ( sharedNodes == 2) ret = SEG_COINCIDENT_SEGMENTS;
    if ( sharedNodes > 2) ret = SEG_SEGMENT_INTERSECTION;
    if ( ret == SEG_COINCIDENT_SEGMENTS) {
      if ( dstSegment) {
	dstSegment->set( s1.begin(), s1.end());
      }
    } else {
      if ( dstPoint) {
	dstPoint->set( tmpP);
      }
    }
    return ret;
  }

  Segment tmpL;
  Segment sAlin;
  Punto pAlin;
  int numAlign = 0;
  if ( fabsf( a1) < eps) { // s1.begin(), s1.end(), s2.begin() == alineados
    sAlin = s1; pAlin = s2.begin();
    numAlign++;
  } 
  if ( fabsf( a2) < eps) { // s1.begin(), s1.end(), s2.end() == alineados
    sAlin = s1; pAlin = s2.end();
    numAlign++;
  } 
  if ( fabsf( b1) < eps) { // s2.begin(), s2.end(), s1.begin() == alineados
    sAlin = s2; pAlin = s1.begin();
    numAlign++;
  } 
  if ( fabsf( b2) < eps) { // s2.begin(), s2.end(), s1.end() == alineados
    sAlin = s2; pAlin = s1.end();
    numAlign++;
  }
  if ( numAlign == 1) { // solo hay un punto alineado
    if ( dentroSegmento( sAlin, pAlin, eps)) {
      if ( dstPoint) {
        dstPoint->set( pAlin);
      }
      return SEG_ENDPOINT_INTERSECTION;
    }
  } else if ( numAlign > 1) { // los dos segmentos estan alineados
    bool dentros1s2beg = dentroSegmento( s1, s2.begin(), eps);
    bool dentros1s2end = dentroSegmento( s1, s2.end(), eps);
    bool dentros2s1beg = dentroSegmento( s2, s1.begin(), eps);
    bool dentros2s1end = dentroSegmento( s2, s1.end(), eps);
    // no puede ser esto: porque ya lo hemos tratado antes
    // if ( dentros1s2beg && dentros1s2end && dentros2s1beg && dentros2s1end) {
    //   *dstSegment = new Segment( s1.begin(), s1.end(), s1.r, s1.g, s1.b, true);
    //   setColor( *dstSegment, SEG_COINCIDENT_SEGMENTS);
    //   return SEG_COINCIDENT_SEGMENTS;
    // }

    _t_segmentIntersectionType ret = SEG_NO_INTERSECTION;
    if ( dentros1s2beg && dentros1s2end) {
      if ( dstSegment) {
	dstSegment->set( s2.begin(), s2.end());
      }
      ret = SEG_INTERIOR_SEGMENT;
    } else if ( dentros2s1beg && dentros2s1end) {
      if ( dstSegment) {
	dstSegment->set( s1.begin(), s1.end());
      }
      ret = SEG_INTERIOR_SEGMENT;
    } else if ( ( dentros1s2beg || dentros1s2end) &&
		( dentros2s1beg || dentros2s1end)) {
      // SEG_SEGMENT_INTERSECTION;
      Punto p1, p2;
      p1 = dentros1s2beg ? s2.begin() : s2.end();
      p2 = dentros2s1beg ? s1.begin() : s1.end();
      if ( dstSegment) {
	dstSegment->set( p1, p2);
      }
      ret = SEG_SEGMENT_INTERSECTION;
    }
    return ret;
  }

  // ahora nos divertimos con los determinantes

  return SEG_NO_INTERSECTION;
}

bool checkIntersection( const std::vector< Segment>lstSegments,
                        const Punto &src, const Punto &dst, Punto *intersec, float eps) {
  Segment tmp;
  Segment travel( src, dst);
  bool ret = false;
  for ( unsigned int i = 0; i < lstSegments.size(); i++) {
    _t_segmentIntersectionType res = checkIntersection( lstSegments[ i], travel, intersec, &tmp, eps);
    // considered as no intersection:
    // no intersection
    // interior segment
    // coincident_segments
    if ( ( res == SEG_INTERIOR_POINT_INTERSECTION) ||
         ( res == SEG_ENDPOINT_INTERSECTION) ||
         ( res == SEG_ENDPOINT_SHARED) ||
         ( res == SEG_SEGMENT_INTERSECTION)) {
      ret = true;
      break;
    }
  }
  return ret;
}

